/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "unit_ecma_test.h"
#include "optimizer/ir/datatype.h"
#include "optimizer/ir/graph_cloner.h"
#include "optimizer/optimizations/cleanup.h"
#include "optimizer/optimizations/phi_type_resolving.h"
#include "optimizer/optimizations/inline_intrinsics.h"
#include "runtime/include/coretypes/tagged_value.h"

namespace ark::compiler {
class InlineIntrinsicsTest : public AsmTest {
public:
    InlineIntrinsicsTest() = default;

    Graph *ConstructGraphWithIntrinsic(RuntimeInterface::IntrinsicId id);
    Graph *ConstructGraphWithIntrinsic(AnyBaseType type, RuntimeInterface::IntrinsicId id,
                                       profiling::AnyInputType allowedType = profiling::AnyInputType::DEFAULT);
    Graph *ConstructGraphWithIntrinsic(AnyBaseType type1, AnyBaseType type2, RuntimeInterface::IntrinsicId id);
    Graph *ConstructGraphWithOpcode1(AnyBaseType type, Opcode opcode);
    Graph *ConstructGraphWithOpcode2(AnyBaseType type, Opcode opcode);

    template <typename T, size_t N>
    using TestArray = std::array<std::tuple<RuntimeInterface::IntrinsicId, AnyBaseType, T>, N>;

    template <typename T>
    Graph *ConstructGraphWithConst(AnyBaseType anyType, T cnst);

    Graph *ConstructGraphWithConst(DataType::Any cnst);

    template <typename T, size_t N>
    void TestLdConsts(TestArray<T, N> tests);
};

// NOLINTBEGIN(readability-magic-numbers)
template <typename T>
Graph *InlineIntrinsicsTest::ConstructGraphWithConst(AnyBaseType anyType, T cnst)
{
    auto graph = CreateGraphDynWithDefaultRuntime();
    GRAPH(graph)
    {
        CONSTANT(0, cnst);
        BASIC_BLOCK(2, -1)
        {
            INST(3, Opcode::CastValueToAnyType).any().AnyType(anyType).Inputs(0);
            INST(4, Opcode::Return).any().Inputs(3);
        }
    }
    return graph;
}

Graph *InlineIntrinsicsTest::ConstructGraphWithConst(DataType::Any cnst)
{
    auto graph = CreateGraphDynWithDefaultRuntime();
    GRAPH(graph)
    {
        CONSTANT(0, cnst);
        BASIC_BLOCK(2, -1)
        {
            INST(4, Opcode::Return).any().Inputs(0);
        }
    }
    return graph;
}

template <typename T, size_t N>
void InlineIntrinsicsTest::TestLdConsts(TestArray<T, N> tests)
{
    for (auto [id, any_type, cnst] : tests) {
        auto graph = ConstructGraphWithIntrinsic(id);
        ASSERT_TRUE(graph->template RunPass<InlineIntrinsics>());
        ASSERT_TRUE(graph->template RunPass<Cleanup>());
        GraphChecker(graph).Check();
        Graph *graphOpt = nullptr;
        if constexpr (std::is_same_v<T, DataType::Any>) {
            graphOpt = ConstructGraphWithConst(cnst);
        } else {
            graphOpt = ConstructGraphWithConst(any_type, cnst);
        }
        ASSERT_TRUE(GraphComparator().Compare(graph, graphOpt));
    }
}

Graph *InlineIntrinsicsTest::ConstructGraphWithIntrinsic(RuntimeInterface::IntrinsicId id)
{
    auto graph = CreateGraphDynWithDefaultRuntime();
    GRAPH(graph)
    {
        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::SaveState).Inputs().SrcVregs({});
            INST(3, Opcode::Intrinsic).any().IntrinsicId(id).Inputs({{DataType::NO_TYPE, 2}});
            INST(4, Opcode::Return).any().Inputs(3);
        }
    }
    return graph;
}

Graph *InlineIntrinsicsTest::ConstructGraphWithIntrinsic(AnyBaseType type, RuntimeInterface::IntrinsicId id,
                                                         profiling::AnyInputType allowedType)
{
    auto graph = CreateGraphDynWithDefaultRuntime();
    GRAPH(graph)
    {
        PARAMETER(0, 0).any();
        PARAMETER(1, 1).any();

        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
            INST(4, Opcode::AnyTypeCheck).any().AnyType(type).AllowedInputType(allowedType).Inputs(0, 2);
            INST(5, Opcode::Intrinsic).any().IntrinsicId(id).Inputs({{DataType::ANY, 4}, {DataType::NO_TYPE, 2}});
            INST(6, Opcode::Return).any().Inputs(5);
        }
    }
    return graph;
}

Graph *InlineIntrinsicsTest::ConstructGraphWithOpcode1(AnyBaseType type, Opcode opcode)
{
    auto graph = CreateGraphDynWithDefaultRuntime();
    auto dateType = AnyBaseTypeToDataType(type);
    GRAPH(graph)
    {
        PARAMETER(0, 0).any();
        PARAMETER(1, 1).any();

        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
            INST(3, Opcode::AnyTypeCheck).any().AnyType(type).Inputs(0, 2);
            INST(5, Opcode::CastAnyTypeValue).SetType(dateType).AnyType(type).Inputs(3);
            INST(7, opcode).SetType(dateType).Inputs(5);
            INST(8, Opcode::CastValueToAnyType).any().AnyType(type).Inputs(7);
            INST(9, Opcode::Return).any().Inputs(8);
        }
    }
    return graph;
}

Graph *InlineIntrinsicsTest::ConstructGraphWithOpcode2(AnyBaseType type, Opcode opcode)
{
    auto graph = CreateGraphDynWithDefaultRuntime();
    auto dateType = AnyBaseTypeToDataType(type);
    GRAPH(graph)
    {
        PARAMETER(0, 0).any();
        PARAMETER(1, 1).any();

        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
            INST(3, Opcode::AnyTypeCheck).any().AnyType(type).Inputs(0, 2);
            INST(4, Opcode::AnyTypeCheck).any().AnyType(type).Inputs(1, 2);
            INST(5, Opcode::CastAnyTypeValue).SetType(dateType).AnyType(type).Inputs(3);
            INST(6, Opcode::CastAnyTypeValue).SetType(dateType).AnyType(type).Inputs(4);
            INST(7, opcode).SetType(dateType).Inputs(5, 6);
            INST(8, Opcode::CastValueToAnyType).any().AnyType(type).Inputs(7);
            INST(9, Opcode::Return).any().Inputs(8);
        }
    }
    return graph;
}

Graph *InlineIntrinsicsTest::ConstructGraphWithIntrinsic(AnyBaseType type1, AnyBaseType type2,
                                                         RuntimeInterface::IntrinsicId id)
{
    auto graph = CreateGraphDynWithDefaultRuntime();
    GRAPH(graph)
    {
        PARAMETER(0, 0).any();
        PARAMETER(1, 1).any();

        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
            INST(3, Opcode::AnyTypeCheck).any().AnyType(type1).Inputs(0, 2);
            INST(4, Opcode::AnyTypeCheck).any().AnyType(type2).Inputs(1, 2);
            INST(5, Opcode::Intrinsic)
                .any()
                .IntrinsicId(id)
                .Inputs({{DataType::ANY, 3}, {DataType::ANY, 4}, {DataType::NO_TYPE, 2}});
            INST(6, Opcode::Return).any().Inputs(5);
        }
    }
    return graph;
}

TEST_F(InlineIntrinsicsTest, LdConst)
{
    using TaggedValue = ark::coretypes::TaggedValue;
    {
        TestArray<uint64_t, 2> tests = {{
            {RuntimeInterface::IntrinsicId::INTRINSIC_LDTRUE, AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE, 1U},
            {RuntimeInterface::IntrinsicId::INTRINSIC_LDFALSE, AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE, 0U},
        }};
        TestLdConsts(tests);
    }
    {
        TestArray<DataType::Any, 3> tests = {{
            {RuntimeInterface::IntrinsicId::INTRINSIC_LDHOLE, AnyBaseType::ECMASCRIPT_HOLE_TYPE,
             DataType::Any(TaggedValue::VALUE_HOLE)},
            {RuntimeInterface::IntrinsicId::INTRINSIC_LDNULL, AnyBaseType::ECMASCRIPT_NULL_TYPE,
             DataType::Any(TaggedValue::VALUE_NULL)},
            {RuntimeInterface::IntrinsicId::INTRINSIC_LDUNDEFINED, AnyBaseType::ECMASCRIPT_UNDEFINED_TYPE,
             DataType::Any(TaggedValue::VALUE_UNDEFINED)},
        }};
        TestLdConsts(tests);
    }
    {
        TestArray<double, 2> tests = {{
            {RuntimeInterface::IntrinsicId::INTRINSIC_LDINFINITY, AnyBaseType::ECMASCRIPT_DOUBLE_TYPE,
             TaggedValue::VALUE_INFINITY},
            {RuntimeInterface::IntrinsicId::INTRINSIC_LDNAN, AnyBaseType::ECMASCRIPT_DOUBLE_TYPE,
             TaggedValue::VALUE_NAN},
        }};
        TestLdConsts(tests);
    }
}

TEST_F(InlineIntrinsicsTest, ToNumber)
{
    // Case with Int
    {
        auto graph = ConstructGraphWithIntrinsic(AnyBaseType::ECMASCRIPT_INT_TYPE,
                                                 RuntimeInterface::IntrinsicId::INTRINSIC_TONUMBER);
        ASSERT_TRUE(graph->RunPass<InlineIntrinsics>());
        graph->RunPass<Cleanup>();
        GraphChecker(graph).Check();

        auto graphOpt = CreateGraphDynWithDefaultRuntime();
        GRAPH(graphOpt)
        {
            PARAMETER(0, 0).any();
            PARAMETER(1, 1).any();

            BASIC_BLOCK(2, -1)
            {
                INST(2, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
                INST(3, Opcode::AnyTypeCheck).any().AnyType(AnyBaseType::ECMASCRIPT_INT_TYPE).Inputs(0, 2);
                INST(9, Opcode::Return).any().Inputs(3);
            }
        }

        ASSERT_TRUE(GraphComparator().Compare(graph, graphOpt));
    }

    // Case with Int when boolean and null inputs are allowed
    {
        auto graph = ConstructGraphWithIntrinsic(AnyBaseType::ECMASCRIPT_INT_TYPE,
                                                 RuntimeInterface::IntrinsicId::INTRINSIC_TONUMBER,
                                                 profiling::AnyInputType::SPECIAL);
        ASSERT_TRUE(graph->RunPass<InlineIntrinsics>());
        graph->RunPass<Cleanup>();
        GraphChecker(graph).Check();

        auto graphOpt = CreateGraphDynWithDefaultRuntime();
        GRAPH(graphOpt)
        {
            PARAMETER(0, 0).any();
            PARAMETER(1, 1).any();

            BASIC_BLOCK(2, -1)
            {
                INST(2, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
                INST(3, Opcode::AnyTypeCheck)
                    .any()
                    .AnyType(AnyBaseType::ECMASCRIPT_INT_TYPE)
                    .AllowedInputType(profiling::AnyInputType::SPECIAL)
                    .Inputs(0, 2);
                INST(4, Opcode::CastAnyTypeValue).s32().AnyType(AnyBaseType::ECMASCRIPT_INT_TYPE).Inputs(3);
                INST(5, Opcode::CastValueToAnyType).any().AnyType(AnyBaseType::ECMASCRIPT_INT_TYPE).Inputs(4);
                INST(9, Opcode::Return).any().Inputs(5);
            }
        }

        ASSERT_TRUE(GraphComparator().Compare(graph, graphOpt));
    }

    // Case with Double
    for (auto allowedType : {profiling::AnyInputType::DEFAULT, profiling::AnyInputType::INTEGER}) {
        auto graph = ConstructGraphWithIntrinsic(AnyBaseType::ECMASCRIPT_DOUBLE_TYPE,
                                                 RuntimeInterface::IntrinsicId::INTRINSIC_TONUMBER, allowedType);
        ASSERT_TRUE(graph->RunPass<InlineIntrinsics>());
        graph->RunPass<Cleanup>();
        GraphChecker(graph).Check();

        auto graphOpt = CreateGraphDynWithDefaultRuntime();
        GRAPH(graphOpt)
        {
            PARAMETER(0, 0).any();
            PARAMETER(1, 1).any();

            BASIC_BLOCK(2, -1)
            {
                INST(2, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
                INST(3, Opcode::AnyTypeCheck).any().AnyType(AnyBaseType::ECMASCRIPT_DOUBLE_TYPE).Inputs(0, 2);
                INST(9, Opcode::Return).any().Inputs(3);
            }
        }

        ASSERT_TRUE(GraphComparator().Compare(graph, graphOpt));
    }

    // Case with Double when boolean, undefined and null inputs are allowed
    {
        auto graph = ConstructGraphWithIntrinsic(AnyBaseType::ECMASCRIPT_DOUBLE_TYPE,
                                                 RuntimeInterface::IntrinsicId::INTRINSIC_TONUMBER,
                                                 profiling::AnyInputType::SPECIAL);
        ASSERT_TRUE(graph->RunPass<InlineIntrinsics>());
        graph->RunPass<Cleanup>();
        GraphChecker(graph).Check();

        auto graphOpt = CreateGraphDynWithDefaultRuntime();
        GRAPH(graphOpt)
        {
            PARAMETER(0, 0).any();
            PARAMETER(1, 1).any();

            BASIC_BLOCK(2, -1)
            {
                INST(2, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
                INST(3, Opcode::AnyTypeCheck)
                    .any()
                    .AnyType(AnyBaseType::ECMASCRIPT_DOUBLE_TYPE)
                    .AllowedInputType(profiling::AnyInputType::SPECIAL)
                    .Inputs(0, 2);
                INST(4, Opcode::CastAnyTypeValue).f64().AnyType(AnyBaseType::ECMASCRIPT_DOUBLE_TYPE).Inputs(3);
                INST(5, Opcode::CastValueToAnyType).any().AnyType(AnyBaseType::ECMASCRIPT_DOUBLE_TYPE).Inputs(4);
                INST(9, Opcode::Return).any().Inputs(5);
            }
        }

        ASSERT_TRUE(GraphComparator().Compare(graph, graphOpt));
    }

    // Case with Object
    {
        auto graph = ConstructGraphWithIntrinsic(AnyBaseType::ECMASCRIPT_OBJECT_TYPE,
                                                 RuntimeInterface::IntrinsicId::INTRINSIC_TONUMBER);
        auto clone = GraphCloner(graph, graph->GetAllocator(), graph->GetLocalAllocator()).CloneGraph();
        ASSERT_FALSE(graph->RunPass<InlineIntrinsics>());
        ASSERT_FALSE(graph->RunPass<Cleanup>());
        GraphChecker(graph).Check();

        ASSERT_TRUE(GraphComparator().Compare(graph, clone));
    }
}

TEST_F(InlineIntrinsicsTest, ResolveLoopPhi)
{
    auto graph = CreateGraphDynWithDefaultRuntime();

    // for (let x = 0; x < 10;)
    // {
    //     if (x < 5) {
    //         x++;
    //     }
    // }
    GRAPH(graph)
    {
        CONSTANT(0, 0).i64();
        CONSTANT(1, 10).i64();
        CONSTANT(2, 5).i64();

        BASIC_BLOCK(2, 3)
        {
            INST(3, Opcode::CastValueToAnyType).any().AnyType(AnyBaseType::ECMASCRIPT_INT_TYPE).Inputs(0);
        }
        BASIC_BLOCK(3, 4, 5)
        {
            INST(4, Opcode::Phi).any().Inputs(3, 4, 19);
            INST(5, Opcode::Phi).any().Inputs(3, 14, 4);
            INST(6, Opcode::CastValueToAnyType).any().AnyType(AnyBaseType::ECMASCRIPT_INT_TYPE).Inputs(1);
            INST(7, Opcode::SaveState).NoVregs();
            INST(8, Opcode::Intrinsic)
                .any()
                .IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_LESS_DYN)
                .Inputs({{DataType::ANY, 4}, {DataType::ANY, 6}, {DataType::NO_TYPE, 7}});
            INST(9, Opcode::CastValueToAnyType).any().AnyType(AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE).Inputs(0);
            INST(10, Opcode::Compare).b().CC(CC_EQ).Inputs(8, 9);
            INST(11, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(10);
        }
        BASIC_BLOCK(4, -1)
        {
            INST(20, Opcode::CastValueToAnyType).any().AnyType(AnyBaseType::ECMASCRIPT_UNDEFINED_TYPE).Inputs(0);
            INST(21, Opcode::Return).any().Inputs(20);
        }
        BASIC_BLOCK(5, 3, 6)
        {
            INST(12, Opcode::CastValueToAnyType).any().AnyType(AnyBaseType::ECMASCRIPT_INT_TYPE).Inputs(2);
            INST(13, Opcode::SaveState).NoVregs();
            INST(14, Opcode::Intrinsic)
                .any()
                .IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_LESS_DYN)
                .Inputs({{DataType::ANY, 4}, {DataType::ANY, 12}, {DataType::NO_TYPE, 13}});
            INST(15, Opcode::CastValueToAnyType).any().AnyType(AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE).Inputs(0);
            INST(16, Opcode::Compare).b().CC(CC_EQ).Inputs(14, 15);
            INST(17, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(16);
        }
        BASIC_BLOCK(6, 3)
        {
            INST(18, Opcode::SaveState).NoVregs();
            INST(19, Opcode::Intrinsic)
                .any()
                .IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_INC_DYN)
                .Inputs({{DataType::ANY, 4}, {DataType::NO_TYPE, 18}});
        }
    }
    ASSERT_TRUE(graph->RunPass<InlineIntrinsics>());
    ASSERT_TRUE(graph->RunPass<PhiTypeResolving>());
    ASSERT_TRUE(graph->RunPass<Cleanup>(false));
    GraphChecker(graph).Check();

    auto &phi = INS(4);
    ASSERT_EQ(phi.GetType(), DataType::INT32);
    ASSERT_EQ(phi.GetInput(1U).GetInst(), &phi);
}

TEST_F(InlineIntrinsicsTest, LdLexVarDynApply)
{
    constexpr uint32_t SLOT = 10U;

    for (size_t level = 0U; level <= InlineIntrinsics::GetLdStLexVarDynLevelThreshold(); ++level) {
        auto graph = CreateGraphDynStubWithDefaultRuntime();
        GRAPH(graph)
        {
            PARAMETER(0, 0).any();
            BASIC_BLOCK(2, -1)
            {
                INST(1, Opcode::LoadLexicalEnv).any().Inputs(0);
                INST(2, Opcode::Intrinsic)
                    .IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_LD_LEX_VAR_DYN)
                    .any()
                    .Inputs({{DataType::ANY, 1}})
                    .AddImm(level)
                    .AddImm(SLOT);
                INST(3, Opcode::Return).any().Inputs(2);
            }
        }

        ASSERT_TRUE(graph->RunPass<InlineIntrinsics>());
        ASSERT_FALSE(graph->RunPass<Cleanup>());
        GraphChecker(graph).Check();

        auto graphOpt = CreateGraphDynStubWithDefaultRuntime();
        GRAPH(graphOpt)
        {
            PARAMETER(11, 0).any();
            CONSTANT(0, graphOpt->GetRuntime()->GetLexicalEnvParentEnvIndex());
            CONSTANT(1, graphOpt->GetRuntime()->GetLexicalEnvStartDataIndex() + SLOT);

            BASIC_BLOCK(2, -1)
            {
                INST(2, Opcode::LoadLexicalEnv).any().Inputs(11);
                INST(3, Opcode::CastAnyTypeValue)
                    .ref()
                    .Inputs(2)
                    .AnyType(AnyBaseType::ECMASCRIPT_ARRAY_TYPE)
                    .SetFlag(inst_flags::NO_HOIST);
                int lastIndex = 3;
                for (auto curLevel = level; curLevel > 0U; --curLevel) {
                    INST(lastIndex + 1, Opcode::LoadArray).any().Inputs(lastIndex, 0);
                    INST(lastIndex + 2, Opcode::CastAnyTypeValue)
                        .ref()
                        .Inputs(lastIndex + 1)
                        .AnyType(AnyBaseType::ECMASCRIPT_ARRAY_TYPE)
                        .SetFlag(inst_flags::NO_HOIST);
                    lastIndex += 2;
                }
                INST(lastIndex + 1, Opcode::LoadArray).any().Inputs(lastIndex, 1);
                INST(lastIndex + 2, Opcode::Return).any().Inputs(lastIndex + 1);
            }
        }

        graphOpt->RunPass<Cleanup>();
        ASSERT_TRUE(GraphComparator().Compare(graph, graphOpt));
    }
}

TEST_F(InlineIntrinsicsTest, LdLexVarDynSkip)
{
    constexpr uint32_t LEVEL = InlineIntrinsics::GetLdStLexVarDynLevelThreshold() + 1U;
    constexpr uint32_t SLOT = 10U;

    auto graph = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graph)
    {
        PARAMETER(0, 0).any();
        BASIC_BLOCK(2, -1)
        {
            INST(1, Opcode::LoadLexicalEnv).any().Inputs(0);
            INST(2, Opcode::Intrinsic)
                .IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_LD_LEX_VAR_DYN)
                .any()
                .Inputs({{DataType::ANY, 1}})
                .AddImm(LEVEL)
                .AddImm(SLOT);
            INST(3, Opcode::Return).any().Inputs(2);
        }
    }

    ASSERT_FALSE(graph->RunPass<InlineIntrinsics>());
    ASSERT_FALSE(graph->RunPass<Cleanup>());
    GraphChecker(graph).Check();

    auto graphOpt = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graphOpt)
    {
        PARAMETER(0, 0).any();
        BASIC_BLOCK(2, -1)
        {
            INST(1, Opcode::LoadLexicalEnv).any().Inputs(0);
            INST(2, Opcode::Intrinsic)
                .IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_LD_LEX_VAR_DYN)
                .any()
                .Inputs({{DataType::ANY, 1}})
                .AddImm(LEVEL)
                .AddImm(SLOT);
            INST(3, Opcode::Return).any().Inputs(2);
        }
    }

    ASSERT_TRUE(GraphComparator().Compare(graph, graphOpt));
}

TEST_F(InlineIntrinsicsTest, LdLexDyn)
{
    constexpr uint32_t STRING_ID = 0xABCDU;
    constexpr uint32_t LEVEL = InlineIntrinsics::GetLdStLexVarDynLevelThreshold() + 1U;
    constexpr uint32_t SLOT = 10U;

    auto graph = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graph)
    {
        PARAMETER(0, 0).any();
        BASIC_BLOCK(2, -1)
        {
            INST(1, Opcode::LoadConstantPool).any().Inputs(0);
            INST(2, Opcode::LoadLexicalEnv).any().Inputs(0);
            INST(3, Opcode::SaveState).NoVregs();
            INST(6, Opcode::LoadFromConstantPool).any().Inputs(1).TypeId(STRING_ID);
            INST(4, Opcode::Intrinsic)
                .IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_LD_LEX_DYN)
                .any()
                .Inputs({{DataType::ANY, 6}, {DataType::ANY, 2}, {DataType::NO_TYPE, 3}})
                .AddImm(LEVEL)
                .AddImm(SLOT);
            INST(5, Opcode::Return).any().Inputs(4);
        }
    }

    ASSERT_TRUE(graph->RunPass<InlineIntrinsics>());
    ASSERT_TRUE(graph->RunPass<Cleanup>());
    GraphChecker(graph).Check();

    auto graphOpt = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graphOpt)
    {
        PARAMETER(0, 0).any();
        CONSTANT(1, DataType::Any(ark::coretypes::TaggedValue::VALUE_HOLE));
        BASIC_BLOCK(2, -1)
        {
            INST(3, Opcode::LoadLexicalEnv).any().Inputs(0);
            INST(4, Opcode::SaveState).NoVregs();
            // We do not check LdLexVarDyn expansion in this test!
            INST(5, Opcode::Intrinsic)
                .IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_LD_LEX_VAR_DYN)
                .any()
                .Inputs({{DataType::ANY, 3}})
                .AddImm(LEVEL)
                .AddImm(SLOT)
                .ClearFlag(inst_flags::REQUIRE_STATE);
            INST(6, Opcode::Compare).b().Inputs(5, 1).SrcType(DataType::ANY).CC(ConditionCode::CC_EQ);
            INST(7, Opcode::DeoptimizeIf).Inputs(6, 4).DeoptimizeType(DeoptimizeType::HOLE);
            INST(8, Opcode::Return).any().Inputs(5);
        }
    }

    ASSERT_TRUE(GraphComparator().Compare(graph, graphOpt));
}

TEST_F(InlineIntrinsicsTest, InlineLdlexenvDyn)
{
    auto graph = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graph)
    {
        PARAMETER(0, 0).any();
        BASIC_BLOCK(2, -1)
        {
            INST(4, Opcode::SaveState).NoVregs();
            INST(1, Opcode::Intrinsic)
                .IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_LDLEXENV_DYN)
                .any()
                .Inputs({{DataType::ANY, 0}})
                .ClearFlag(inst_flags::REQUIRE_STATE);
            INST(2, Opcode::Return).any().Inputs(1);
        }
    }
    ASSERT_TRUE(graph->RunPass<InlineIntrinsics>());
    ASSERT_TRUE(graph->RunPass<Cleanup>());
    GraphChecker(graph).Check();
    auto graphOpt = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graphOpt)
    {
        PARAMETER(0, 0).any();
        BASIC_BLOCK(2, -1)
        {
            INST(1, Opcode::LoadLexicalEnv).any().Inputs(0);
            INST(2, Opcode::Return).any().Inputs(1);
        }
    }
    GraphChecker(graphOpt).Check();
    ASSERT_TRUE(GraphComparator().Compare(graph, graphOpt));
}

TEST_F(InlineIntrinsicsTest, StLexVarDyn)
{
    constexpr uint32_t SLOT = 10U;

    size_t level = 0U;
    auto graph = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graph)
    {
        PARAMETER(0, 0).any();
        CONSTANT(1, 1);
        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::LoadLexicalEnv).any().Inputs(0);
            INST(3, Opcode::Intrinsic)
                .IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_ST_LEX_VAR_DYN)
                .any()
                .Inputs({{DataType::INT32, 1}, {DataType::ANY, 2}})
                .AddImm(level)
                .AddImm(SLOT)
                .ClearFlag(inst_flags::REQUIRE_STATE);
            INST(4, Opcode::ReturnVoid).v0id();
        }
    }
    ASSERT_TRUE(graph->RunPass<InlineIntrinsics>());
    ASSERT_FALSE(graph->RunPass<Cleanup>());
    GraphChecker(graph).Check();
    auto graphOpt = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graphOpt)
    {
        PARAMETER(0, 0).any();
        CONSTANT(1, 1);
        CONSTANT(7, graphOpt->GetRuntime()->GetLexicalEnvStartDataIndex() + SLOT);

        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::LoadLexicalEnv).any().Inputs(0);
            INST(3, Opcode::CastAnyTypeValue)
                .ref()
                .Inputs(2)
                .AnyType(AnyBaseType::ECMASCRIPT_ARRAY_TYPE)
                .SetFlag(inst_flags::NO_HOIST);
            INST(5, Opcode::StoreArray).any().Inputs(3).Inputs(7).Inputs(1).SetNeedBarrier(true);
            INST(4, Opcode::ReturnVoid).v0id();
        }
    }
    graphOpt->RunPass<Cleanup>();
    GraphChecker(graphOpt).Check();
    ASSERT_TRUE(GraphComparator().Compare(graph, graphOpt));

    level = 1U;
    graph = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graph)
    {
        PARAMETER(0, 0).any();
        CONSTANT(1, 1);
        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::LoadLexicalEnv).any().Inputs(0);
            INST(3, Opcode::Intrinsic)
                .IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_ST_LEX_VAR_DYN)
                .any()
                .Inputs({{DataType::INT32, 1}, {DataType::ANY, 2}})
                .AddImm(level)
                .AddImm(SLOT)
                .ClearFlag(inst_flags::REQUIRE_STATE);
            INST(4, Opcode::ReturnVoid).v0id();
        }
    }
    ASSERT_TRUE(graph->RunPass<InlineIntrinsics>());
    ASSERT_FALSE(graph->RunPass<Cleanup>());
    GraphChecker(graph).Check();

    graphOpt = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graphOpt)
    {
        PARAMETER(0, 0).any();
        CONSTANT(1, 1);
        CONSTANT(7, graphOpt->GetRuntime()->GetLexicalEnvParentEnvIndex());
        CONSTANT(10, graphOpt->GetRuntime()->GetLexicalEnvStartDataIndex() + SLOT);

        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::LoadLexicalEnv).any().Inputs(0);
            INST(5, Opcode::CastAnyTypeValue)
                .ref()
                .Inputs(2)
                .AnyType(AnyBaseType::ECMASCRIPT_ARRAY_TYPE)
                .SetFlag(inst_flags::NO_HOIST);
            INST(6, Opcode::LoadArray).any().Inputs(5, 7);
            INST(8, Opcode::CastAnyTypeValue)
                .ref()
                .Inputs(6)
                .AnyType(AnyBaseType::ECMASCRIPT_ARRAY_TYPE)
                .SetFlag(inst_flags::NO_HOIST);
            INST(9, Opcode::StoreArray).any().Inputs(8).Inputs(10).Inputs(1).SetNeedBarrier(true);
            INST(4, Opcode::ReturnVoid).v0id();
        }
    }
    graphOpt->RunPass<Cleanup>();
    ASSERT_TRUE(GraphComparator().Compare(graph, graphOpt));

    level = 2U;
    graph = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graph)
    {
        PARAMETER(0, 0).any();
        CONSTANT(1, 1);
        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::LoadLexicalEnv).any().Inputs(0);
            INST(3, Opcode::Intrinsic)
                .IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_ST_LEX_VAR_DYN)
                .any()
                .Inputs({{DataType::INT32, 1}, {DataType::ANY, 2}})
                .AddImm(level)
                .AddImm(SLOT)
                .ClearFlag(inst_flags::REQUIRE_STATE);
            INST(4, Opcode::ReturnVoid).v0id();
        }
    }
    ASSERT_TRUE(graph->RunPass<InlineIntrinsics>());
    ASSERT_FALSE(graph->RunPass<Cleanup>());
    GraphChecker(graph).Check();

    graphOpt = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graphOpt)
    {
        PARAMETER(0, 0).any();
        CONSTANT(1, 1);
        CONSTANT(7, graphOpt->GetRuntime()->GetLexicalEnvParentEnvIndex());
        CONSTANT(12, graphOpt->GetRuntime()->GetLexicalEnvStartDataIndex() + SLOT);

        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::LoadLexicalEnv).any().Inputs(0);
            INST(5, Opcode::CastAnyTypeValue)
                .ref()
                .Inputs(2)
                .AnyType(AnyBaseType::ECMASCRIPT_ARRAY_TYPE)
                .SetFlag(inst_flags::NO_HOIST);
            INST(6, Opcode::LoadArray).any().Inputs(5, 7);
            INST(8, Opcode::CastAnyTypeValue)
                .ref()
                .Inputs(6)
                .AnyType(AnyBaseType::ECMASCRIPT_ARRAY_TYPE)
                .SetFlag(inst_flags::NO_HOIST);
            INST(9, Opcode::LoadArray).any().Inputs(8, 7);
            INST(10, Opcode::CastAnyTypeValue)
                .ref()
                .Inputs(9)
                .AnyType(AnyBaseType::ECMASCRIPT_ARRAY_TYPE)
                .SetFlag(inst_flags::NO_HOIST);
            INST(11, Opcode::StoreArray).any().Inputs(10).Inputs(12).Inputs(1).SetNeedBarrier(true);
            INST(4, Opcode::ReturnVoid).v0id();
        }
    }
    graphOpt->RunPass<Cleanup>();
    ASSERT_TRUE(GraphComparator().Compare(graph, graphOpt));

    level = 3U;
    graph = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graph)
    {
        PARAMETER(0, 0).any();
        CONSTANT(1, 1);
        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::LoadLexicalEnv).any().Inputs(0);
            INST(3, Opcode::Intrinsic)
                .IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_ST_LEX_VAR_DYN)
                .any()
                .Inputs({{DataType::INT32, 1}, {DataType::ANY, 2}})
                .AddImm(level)
                .AddImm(SLOT)
                .ClearFlag(inst_flags::REQUIRE_STATE);
            INST(4, Opcode::ReturnVoid).v0id();
        }
    }
    ASSERT_FALSE(graph->RunPass<InlineIntrinsics>());
    ASSERT_FALSE(graph->RunPass<Cleanup>());
    GraphChecker(graph).Check();

    graphOpt = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graphOpt)
    {
        PARAMETER(0, 0).any();
        CONSTANT(1, 1);
        BASIC_BLOCK(2, -1)
        {
            INST(2, Opcode::LoadLexicalEnv).any().Inputs(0);
            INST(3, Opcode::Intrinsic)
                .IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_ST_LEX_VAR_DYN)
                .any()
                .Inputs({{DataType::INT32, 1}, {DataType::ANY, 2}})
                .AddImm(level)
                .AddImm(SLOT)
                .ClearFlag(inst_flags::REQUIRE_STATE);
            INST(4, Opcode::ReturnVoid).v0id();
        }
    }
    graphOpt->RunPass<Cleanup>();
    ASSERT_TRUE(GraphComparator().Compare(graph, graphOpt));
}

TEST_F(InlineIntrinsicsTest, PopLexenvDyn)
{
    auto graph = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graph)
    {
        PARAMETER(0, 0).any();
        BASIC_BLOCK(2, -1)
        {
            INST(1, Opcode::LoadLexicalEnv).any().Inputs(0);
            INST(2, Opcode::SaveState).NoVregs();
            INST(3, Opcode::Intrinsic)
                .IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_POP_LEXENV_DYN)
                .any()
                .Inputs({{DataType::ANY, 1}, {DataType::NO_TYPE, 2}});
            INST(4, Opcode::Return).any().Inputs(3);
        }
    }

    ASSERT_TRUE(graph->RunPass<InlineIntrinsics>());
    GraphChecker(graph).Check();

    auto graphOpt = CreateGraphDynStubWithDefaultRuntime();
    GRAPH(graphOpt)
    {
        PARAMETER(0, 0).any();
        CONSTANT(5, graphOpt->GetRuntime()->GetLexicalEnvParentEnvIndex());
        BASIC_BLOCK(2, -1)
        {
            INST(1, Opcode::LoadLexicalEnv).any().Inputs(0);
            INST(2, Opcode::SaveState).NoVregs();
            INST(4, Opcode::CastAnyTypeValue)
                .ref()
                .Inputs(1)
                .AnyType(AnyBaseType::ECMASCRIPT_ARRAY_TYPE)
                .SetFlag(inst_flags::NO_HOIST);
            INST(6, Opcode::LoadArray).any().Inputs(4, 5);
            INST(3, Opcode::Return).any().Inputs(6);
        }
    }

    ASSERT_TRUE(GraphComparator().Compare(graph, graphOpt));
}
// NOLINTEND(readability-magic-numbers)

}  // namespace ark::compiler
